package org.summerframework.component.security.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.summerframework.component.security.annotion.RoleRequired;
import org.summerframework.component.security.events.PermissionsUpdateEvent;
import org.summerframework.component.security.permissions.service.UserPermissionsService;
import org.summerframework.component.security.permissions.service.dto.UserPermissionsDTO;
import org.summerframework.component.security.security.service.DefaultRoleService;
import org.summerframework.core.base.result.DefaultResultCode;
import org.summerframework.core.exception.WebException;
import org.summerframework.core.util.SpringContextUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import static org.summerframework.component.security.core.interceptor.SecurityInterceptor.ADMIN;
import static org.summerframework.component.security.core.interceptor.SecurityInterceptor.ANY;

/**
 * @author 石超
 * @version v1.0.0
 */
@Configuration
@ConditionalOnProperty(name = "application.menu2", havingValue = "true")
public class Menu2Configuration implements ApplicationListener<ApplicationStartedEvent> {
    private static final ConfigurationPropertyName CLIENT_LOG = ConfigurationPropertyName.of("permissions.list");
    private static final ConfigurationPropertyName PARENT_ID = ConfigurationPropertyName.of("permissions.parent-id");

    private static final Bindable<Map<String, MenuProperties>> STRING_CLIENT_LOG_PROPERTY_MAP = Bindable.of(ResolvableType.forClassWithGenerics(LinkedHashMap.class, String.class, MenuProperties.class));
    private static final Bindable<Long> PARENT_ID_BINDABLE = Bindable.of(Long.class);

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private UserPermissionsService userPermissionsService;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private Environment environment;

    @Resource
    private DefaultRoleService defaultRoleService;

    private Map<RequestMappingInfo, HandlerMethod> requestMappingInfoHandlerMethodHashMap;

    private Map<String, Long> parentIds = new HashMap<>();

    private void selectByParentId(List<UserPermissionsDTO> list, Long parentId) {
        List<UserPermissionsDTO> permissionsList = userPermissionsService.selectByParentId(parentId);

        list.addAll(permissionsList);

        if (CollectionUtils.isNotEmpty(permissionsList)) {
            permissionsList.forEach(permissions -> selectByParentId(list, permissions.getId()));
        }
    }

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        RequestMappingHandlerMapping mappings = applicationContext.getBean(RequestMappingHandlerMapping.class);
        requestMappingInfoHandlerMethodHashMap = new HashMap<>(mappings.getHandlerMethods());


        Binder binder = Binder.get(environment);
        Map<String, MenuProperties> map = binder.bind(CLIENT_LOG, STRING_CLIENT_LOG_PROPERTY_MAP).orElseGet(Collections::emptyMap);
        Long parentId = binder.bind(PARENT_ID, PARENT_ID_BINDABLE).orElseGet(() -> 1L);
        Map<Long, Map<String, MenuProperties>> collect;

        if (parentId < 0) {
            collect = map.keySet().stream().collect(Collectors.groupingBy(key -> map.get(key).getParentId(), Collector.of(LinkedHashMap::new, (list, key) -> list.put(key, map.get(key)),
                    (left, right) -> {
                        left.putAll(right);
                        return left;
                    })));
        } else {
            collect = new LinkedHashMap<>();
            collect.put(parentId, map);
        }

        transactionTemplate.execute(transactionStatus -> {
            try {
                System.setProperty("menu", "false");


                collect.forEach((parentIds, menuPropertiesMap) -> {
                    List<UserPermissionsDTO> userPermissionsDTOS = new ArrayList<>();
                    selectByParentId(userPermissionsDTOS, parentIds);
                    List<String> codes = userPermissionsDTOS.stream().map(UserPermissionsDTO::getCode).collect(Collectors.toList());

                    child(parentIds, menuPropertiesMap, codes);
                    beanClass(menuPropertiesMap, codes);
                    userPermissionsDTOS.stream().filter(permissions -> codes.contains(permissions.getCode())).map(UserPermissionsDTO::getId).sorted(Comparator.reverseOrder()).forEach(userPermissionsService::deleteById);
                });


                defaultRoleService.keys().forEach(key -> {
                    String userId = key.replace(DefaultRoleService.ROLE_SAVE_KEY, "");
                    defaultRoleService.parse(userId);
                    SpringContextUtil.publishEvent(new PermissionsUpdateEvent(userId, PermissionsUpdateEvent.PermissionsTypeEnum.ALL));
                });
            } catch (Exception e) {
                transactionStatus.setRollbackOnly();
                e.printStackTrace();
            } finally {
                System.setProperty("menu", "true");
            }

            return null;
        });


        System.out.println("完成");
    }

    private void child(Long parentId, Map<String, MenuProperties> map, List<String> codes) {
        AtomicInteger atomicInteger = new AtomicInteger(0);

        map.forEach((key, menuRequired) -> {
            menuRequired.setCode(key);
            UserPermissionsDTO userPermissionsDTO = new UserPermissionsDTO();
            userPermissionsDTO.setCode(menuRequired.getCode());
            userPermissionsDTO.setName(menuRequired.getName());
            int soreIndex = menuRequired.getSoreIndex();

            if (soreIndex < 0) {
                soreIndex = 0;
            }

            userPermissionsDTO.setSortIndex(atomicInteger.get() >= 0 ? atomicInteger.getAndIncrement() : soreIndex);
            userPermissionsDTO.setType(1);
            userPermissionsDTO.setParentId(parentId);
            userPermissionsDTO.setUrl(menuRequired.getUrl());
            userPermissionsDTO.setIcon(menuRequired.getIcon());
            userPermissionsDTO.setIsSystem(1);
            userPermissionsDTO.setCreateBy(1L);
            userPermissionsDTO.setUpdateBy(1L);
            userPermissionsService.insert(userPermissionsDTO);
            parentIds.put(menuRequired.getCode(), userPermissionsDTO.getId());
            codes.remove(menuRequired.getCode());
            menuRequired.setParentId(parentId);
            menuRequired.setId(userPermissionsDTO.getId());

            if (menuRequired.getChild() != null) {
                child(userPermissionsDTO.getId(), menuRequired.getChild(), codes);
            }
        });
    }

    private void beanClass(Map<String, MenuProperties> map, List<String> codes) {
        List<String> addCodes = new ArrayList<>();
        map.forEach((key, menuRequired) -> {
            if (menuRequired.getChild() == null) {
                Class<?>[] beanClassArray = menuRequired.getBeanClass();
                if (beanClassArray != null && beanClassArray.length > 0) {
                    Arrays.stream(beanClassArray).forEach(beanClass -> {
                        AtomicInteger sortIndex = new AtomicInteger(0);

                        requestMappingInfoHandlerMethodHashMap.forEach((requestMappingInfo, handlerMethod) -> {
                            if (handlerMethod.getBeanType() == beanClass) {
                                Method method = handlerMethod.getMethod();

                                RoleRequired roleRequired1 = AnnotationUtils.findAnnotation(method, RoleRequired.class);
                                RoleRequired.List list = AnnotationUtils.findAnnotation(method, RoleRequired.List.class);

                                Consumer<RoleRequired> consumer = roleRequired -> {
                                    if (roleRequired == null) return;

                                    AtomicInteger i = new AtomicInteger(0);

                                    Arrays.stream(roleRequired.code()).forEach(role -> {
                                        if (StringUtils.equals(role, ADMIN) || StringUtils.equals(role, ANY) || addCodes.contains(role))
                                            return;
                                        UserPermissionsDTO interfaceUserPermissionsDTO = new UserPermissionsDTO();
                                        interfaceUserPermissionsDTO.setCode(role);

                                        if (roleRequired.name().length <= i.get() || roleRequired.name()[i.get()].equals("超级管理员")) {
                                            return;
                                        }

                                        int sort = sortIndex.getAndIncrement();

                                        sort = roleRequired.sortIndex() != 0 ? roleRequired.sortIndex() : sort;
                                        interfaceUserPermissionsDTO.setSortIndex(sort);
                                        interfaceUserPermissionsDTO.setType(2);
                                        interfaceUserPermissionsDTO.setName(roleRequired.name()[i.get()]);
                                        interfaceUserPermissionsDTO.setUrl(new ArrayList<>(requestMappingInfo.getPatternsCondition().getPatterns()).get(0));

                                        if (StringUtils.isNotBlank(roleRequired.parentCode())) {
                                            if (parentIds.get(roleRequired.parentCode()) != null) {
                                                interfaceUserPermissionsDTO.setParentId(parentIds.get(roleRequired.parentCode()));
                                            } else {
                                                throw new WebException(DefaultResultCode.ILLEGAL_ARGUMENT, roleRequired.parentCode() + "不在顶级中");
                                            }
                                        } else {
                                            interfaceUserPermissionsDTO.setParentId(menuRequired.getId());
                                        }

                                        try {
                                            interfaceUserPermissionsDTO.setRequestMappingInfo(objectMapper.writeValueAsString(requestMappingInfo));
                                        } catch (JsonProcessingException e) {
                                            e.printStackTrace();
                                        }

                                        interfaceUserPermissionsDTO.setIsSystem(1);
                                        interfaceUserPermissionsDTO.setCreateBy(1L);
                                        interfaceUserPermissionsDTO.setUpdateBy(1L);

                                        userPermissionsService.insert(interfaceUserPermissionsDTO);
                                        codes.remove(role);
                                        addCodes.add(role);
                                        i.incrementAndGet();
                                    });
                                };

                                if (list != null) {
                                    Arrays.stream(list.value()).forEach(consumer);
                                } else {
                                    consumer.accept(roleRequired1);
                                }
                            }
                        });
                    });
                }
            } else {
                beanClass(menuRequired.getChild(), codes);
            }
        });

    }
}

